function TextArea(p_ctx, p_left, p_top)
{	
/*======================================== PRIVATE ==========================================*/
	this.cursor = 0;
	this.cursor_char = "|";
	this.cursor_color = "blue";	
	this.clip_board = "";
/*====================================== PROPERTIES =========================================*/
	this.minimumWidth = 0;
	this.minimumHeight = 0;
    
    	this.setMarginTop = function(p_margin) { this.margin_top = p_margin; }
	this.getMarginTop = function() { return this.margin_top; }
	this.setMarginTop (5);

	this.setMarginLeft = function(p_margin) { this.margin_left = p_margin; }
	this.getMarginLeft = function() { return this.margin_left; }
	this.setMarginLeft (5);

	this.setSelectionStart = function(p_index) { this.selection_start = p_index; }
	this.getSelectionStart = function() { return this.selection_start; }
	this.setSelectionStart(null);

	this.setSelectionEnd = function(p_index) { this.selection_end = p_index; }
	this.getSelectionEnd = function() { return this.selection_end; }
	this.setSelectionEnd(null);
	
	this.setBackColor = function(p_color) { this.back_color = p_color; }
	this.getBackColor = function() { return this.back_color; }
	this.setBackColor("white");

	this.setSelectionColor = function(p_color) { this.selection_color = p_color; }
	this.getSelectionColor = function() { return this.selection_color; }
	this.setSelectionColor("orange");

	this.setForeColor = function(p_color) { this.fore_color = p_color; }
	this.getForeColor = function() { return this.fore_color; }
	this.setForeColor("black");

	this.getHasFocus= function() { return this.hasFocus ? this.hasFocus : false; }
	this.setHasFocus = function(p_focus)  { this.hasFocus=p_focus; }
	this.setHasFocus(false);
		
	this.getContext = function() { return this.ctx; }
	this.setContext = function(p_ctx) 
	{ 
		try
		{
			if (p_ctx) 
			{
				this.ctx = p_ctx;
 				this.fixed_char_width = this.ctx.measureText("M").width;
 				this.fixed_char_height = this.ctx.measureText("M").width;
			}
			else
				this.ctx = null; // big blue school hamilton : Cailin
		}
		catch (exc) 
		{ 
			alert(exc);
		}
	}
	this.setContext (p_ctx? p_ctx : null);	

	this.getMultiLine = function() { return this.multi_line; }
	this.setMultiLine = function (p_multiline) { this.multi_line = p_multiline; }
	this.setMultiLine(true);

	this.getfixedWidthCharacters = function() { return this.fixed_width_characters; }
	this.setfixedWidthCharacters = function (p_fixed_width_characters) { this.fixed_width_characters = p_fixed_width_characters; }
	this.setfixedWidthCharacters(true);

	this.setText = function(p_text) { this.text=p_text; }
	this.getText = function() { return this.text; }
	this.setText("");
	
	this.getMinHeight = function() { return this.min_height; }
	this.setMinHeight = function(p_min_height) { if (p_min_height>0) this.min_height = p_min_height; }
	this.setMinHeight(20);

	this.getMinWidth = function() { return this.min_width; }
	this.setMinWidth = function(p_min_width) { if (p_min_width>0) this.min_width = p_min_width; }	
	this.setMinWidth(40);

	this.getCharacterSpacing = function()  { return this.character_spacing; }
	this.setCharacterSpacing = function(p_char_spacing) { this.character_spacing=p_char_spacing; }
	this.setCharacterSpacing(1);

	this.getLineSpacing = function() { return this.line_spacing; }
	this.setLineSpacing = function(p_line_spacing) { this.line_spacing=p_line_spacing; }
	this.setLineSpacing(1.5);

	this.getHeight = function() { return this.margin_top + Math.max((this.fixed_char_height * this.line_spacing * this.getNumLines()), this.min_height); }
	this.getWidth = function()
	{
		if (this.fixed_width_characters)
			return Math.max(this.margin_left + this.margin_right + (this.fixed_char_width * this.character_spacing * this.getLongestLineLength().numchars), this.min_width);
		else
			return Math.max(this.margin_left + this.margin_right + this.fixed_char_width + this.getLongestLineLength().pixels, this.min_width);
	}
	
	this.margin_right = 10;
	
	this.getFont = function() { return this.font; }
	this.setFont = function(p_font) { this.font=p_font; }
	this.setFont("11pt Arial");
	
	this.getTop = function() { return this.top; }
	this.setTop = function(p_top) { if (p_top >=0) this.top=p_top; }
	this.setTop(p_top? p_top : 0);

	this.getLeft = function() { return this.left; }
	this.setLeft = function(p_left) { if (p_left >=0) this.left=p_left; }
	this.setLeft(p_left? p_left : 0);


	this.asDOM = function() { return "";}
	this.asHTML = function() { return "";}
	this.intellilist = null;
	//this.htmlTags = (JSONFromXML("tags.xml")).tags.tag;	
	this.ASCII_PRINTABLE_START = 32;
	this.ASCII_PRINTABLE_END = 126; 	
	
/*====================================== EVENTS =========================================*/
	this.onTextChanged = null;

/*====================================== METHODS =========================================*/
	this.getNumLines = function()
	{ 
		var k = 1; 
		for (var i=0; i<this.text.length; i++)
			if (this.text[i] == "\n") k++; 
		return k; 
	}
	this.getLongestLineLength = function() // returns both the character and pixel length
	{ 
		var len = new Object();
		len.numchars = 0;
		len.pixels = 0;
		var l = 0;
		for (var i=0; i<this.text.length; i++)
		{	
			if (this.text[i] == "\n") 
			{
				if (l>len.numchars) 
				{
					len.numchars = l;
					len.pixels = this.ctx.measureText(this.text.substr(i-l, i-1)).width;						
				}
				l = 0;
			}
			else l++;			 
		}
		if ( l > len.numchars ) 
		{
			len.numchars = l;
			len.pixels = this.ctx.measureText(this.text.substr(i-l, i-1)).width;						
		}
		return len; 
	}
	this.getRowCursor = function(p_cursor)
	{
		var row = 0;
		var cursor = p_cursor? p_cursor : this.cursor;
		for (var i=0; i<cursor; i++) if (this.text[i] == "\n") row++;
		return row;
	}

	this.getColCursor=function(p_cursor)
	{
		var last_new_line_pos = 0;
		var cursor = p_cursor? p_cursor : this.cursor;
		for (var i=0; i<cursor; i++) if (this.text[i] == "\n") last_new_line_pos=i+1;
		return cursor - last_new_line_pos ;
	}

	this.getCursor = function(p_col_cursor, p_row_cursor)
	{
		if (p_col_cursor < 0) p_col_cursor = 0;
		if (p_row_cursor < 0) { p_row_cursor = 0; p_col_cursor = 0; }
		var cursor=0;
		for (var i=0; i<p_row_cursor; i++)
		{
			var p = this.text.indexOf("\n", cursor);
			if (p>=0)
				cursor= p+1;	//advance cursor to start of p_cursor_row
			else
				return this.text.length;
		}
		var next_new_line_pos = this.text.indexOf("\n",cursor);
		return Math.min(this.text.length, (next_new_line_pos >= 0)? (Math.min(cursor + p_col_cursor, next_new_line_pos)) : (cursor + p_col_cursor));
	}
	this.getMouseCursorFromTextCursor = function(p_cursor)
	{
		// line spacing is always fixed, irrespective of whether fixed_width_characters is set or not
		var cursor = p_cursor? p_cursor : this.cursor;
		p_y = this.top + this.margin_top + (this.getRowCursor(cursor) * (this.fixed_char_height*this.line_spacing));
		if (this.fixed_width_characters)
			p_x = this.left + this.margin_left + (this.getColCursor(cursor) * (this.fixed_char_width*this.character_spacing));
		else
		{
			var cursor_line_text = this.text.split("\n")[this.getRowCursor(cursor)];	
			if (cursor_line_text) 
				p_x = this.left + this.margin_left + this.ctx.measureText(cursor_line_text.substring(0,this.getColCursor(cursor))).width-this.ctx.measureText('x').width;
		}
		return new Point(p_x, p_y);		
	}

	
	this.getTextCursorFromMouseCursor = function(p_x, p_y)
	{
		var cursor_y = Math.floor((p_y-this.margin_top)/(this.fixed_char_height*this.line_spacing));
		var cursor_x = null;
		if (this.fixed_width_characters)
			cursor_x = Math.floor((p_x-this.margin_left)/(this.fixed_char_width * this.character_spacing));
		else
		{
			var cursor_line_text = this.text.split("\n")[cursor_y];	
			if (cursor_line_text)
			{
				cursor_line_text = cursor_line_text + " "; //padding
				for (var i = 0; i < cursor_line_text.length; i++)
				{
					if ((this.margin_left + this.ctx.measureText(cursor_line_text.substring(0,i)).width) - (this.ctx.measureText("a").width/2) >= p_x)
					{
						cursor_x = i-1;
						break;
					}			
				}
				if (cursor_x===null) 
					cursor_x = cursor_line_text.length + 10; // return an X cursor that is beyond the end of the cursor linr
			}
			//else cursor_line_text=0;
				
		}
		return this.getCursor(cursor_x, cursor_y);		
	}
	this.click = function(p_x, p_y) { if (this.onClick) this.onClick(p_x, p_y); }
	this.mouseOver = function(p_x, p_y) { if (this.onMouseOver) this.onMouseOver(p_x, p_y); }
	this.mouseDown = function(p_x, p_y)
	{
		this.cursor = this.getTextCursorFromMouseCursor(p_x, p_y);
		this.selection_start = this.cursor;
		this.selection_end = null;
		this.dragging_to_select = true;
		if (this.onMouseDown ) this.onMouseDown(p_x, p_y);
	}
	this.mouseUp = function(p_x, p_y)
	{
		//alert('mouseup');
		this.selection_end = this.getTextCursorFromMouseCursor(p_x, p_y);
		this.dragging_to_select = false;
		if (this.onMouseUp) this.onMouseUp(p_x, p_y);
		//alert("selection: " + this.selection_start + ", " + this.selection_end);
		//alert(hasValue(this.selection_start) && hasValue(this.selection_end) && (0 >= Math.min(this.selection_start, this.selection_end)) && (0 < Math.max(this.selection_start, this.selection_end)));
	}
	this.mouseMove = function(p_x, p_y)
	{
		if (this.dragging_to_select)
			this.selection_end = this.getTextCursorFromMouseCursor(p_x, p_y);
		//alert('mouse move');
	}
	this.deleteSelectedText = function()
	{
		var start = Math.min(this.selection_start, this.selection_end);
		var end = Math.max(this.selection_start, this.selection_end);
		this.text = this.text.fStrRemoveAt(start, end-start);
		this.cursor=start;
		this.setHasFocus(true);
		this.selection_start = null;
	}
	this.doubleClick= function(p_x, p_y)
	{
		//alert('textarea double click');
	}
	this.keyDown = function (key) //for control key events
	{
	    //alert(key);
	    if ((key == 86 && this.ctrl) || (key == 88 && this.ctrl) || key == 17 || key == 9 || key == 35 || key == 37 || key == 38 || key == 39 || key == 40 || key == 13 || key == 8 || key == 46 || key == 35) //control characters
	    {
	        try
	        {
	            this.intellilist = null;
	            if (key == 13) 	//return key
	            {
	                if (this.multi_line == true)
	                {
	                    this.text = this.text.fInsert("\n", this.cursor);
	                    this.cursor++;
	                }
	            }
	            else if (key == 9) 	//tab key
	            {
	                this.text = this.text.fInsert("\t", this.cursor);
	                this.cursor++;
	            }
	            else if (key == 17) 	//control key
	            {
	                this.ctrl = true;
	            }
	            else if (key == 86) 	//v key, implies paste
	            {
	                this.text = this.text.fInsert(this.clip_board, this.cursor);
	                this.cursor += this.clip_board.length;
	                this.selection_start = null;
	                this.selection_end = null;
	            }
	            else if (key == 88) 	//x key, implies cut
	            {
	                var start = Math.min(this.selection_start, this.selection_end);
	                var end = Math.max(this.selection_start, this.selection_end);
	                this.clip_board = this.text.substring(start, end);
	                this.deleteSelectedText();
	            }
	            
	            else if (key == 35) 	//end key
	            {
	                this.cursor = this.getCursor(this.getLongestLineLength().numchars + 1, this.getRowCursor());
	            }
	            else if (key == 36) 	//end key
	            {
	                this.cursor = this.getCursor(0, this.getRowCursor());
	            }
	            else if (key == 8)		//backspace key
	            {
	                if (this.cursor > 0)
	                {
	                    this.cursor--;
	                    this.text = this.text.fStrRemoveAt(this.cursor);
	                }
	            }
	            else if (key == 46)		//delete key
	            {
	                if (hasValue(this.selection_start) && hasValue(this.selection_end))
	                {
	                    this.deleteSelectedText();
	                }
	                else if (this.cursor < this.text.length)
	                {
	                    this.text = this.text.fStrRemoveAt(this.cursor);
	                }
	            }
	            else if (key == 38) 	//up
	            {
	                //alert('New row cursor: '+Math.max(this.getRowCursor()-1,0));
	                //alert('New column cursor: '+this.getColCursor());
	                this.cursor = this.getCursor(this.getColCursor(), this.getRowCursor() - 1);
	                //alert(this.cursor);
	            }
	            else if (key == 40) 	//down
	            {
	                this.cursor = this.getCursor(this.getColCursor(), this.getRowCursor() + 1);
	            }
	            else if (key == 37) 	//left
	            {
	                this.cursor = Math.max(this.cursor - 1, 0);
	            }
	            else if (key == 39) 	//right
	            {
	                this.cursor = Math.min(this.cursor + 1, this.text.length);
	            }
	            this.render();
	            var rslt = false;
	        }
	        catch (exc)
	        {
	            alert(exc);
	        }
	    }
	    else
	    {
	        var rslt = true;
	    }
	    if (this.onTextChanged) 
            this.onTextChanged(this.text);
	    if (this.onKeyDown) 
            this.onKeyDown(key);
	    return rslt;
	}
	
	this.keyUp = function(key)
	{
		if (key == 39) this.ctrl=false; //release control flag
	}

	this.keyPress = function (key) //for printable character keys
	{
	    //alert(this.onTextChanged);
        try
	    {
	        //alert(this.getLongestLineLength());
	        this.intellilist = null;
	        if (key >= this.ASCII_PRINTABLE_START && key <= this.ASCII_PRINTABLE_END) //key is printable character code
	        {
	            if (hasValue(this.selection_start) && hasValue(this.selection_end))
	            {
	                this.deleteSelectedText();
	            }
	            var ch = String.fromCharCode(key);
	            this.text = this.text.fInsert(ch, this.cursor);
	            this.cursor++;
	            if (ch == ">" && (this.multi_line == true))
	            {
	                var line = this.text.substr(Math.max(this.text.lastIndexOf("\n"), 0));
	                var idx = line.indexOf("<");
	                if ((idx >= 0) && (idx < (this.cursor))) //we have a tag
	                {
	                    var strtag = line.substring(idx + 1, this.cursor - 1);
	                    var closure = "";
	                    for (var i = 0; i < this.htmlTags.length; i++)
	                        if (this.htmlTags[i].name == strtag)
	                        {
	                            closure = this.htmlTags[i].closure;
	                            break;
	                        }
	                    if (closure == "normal") 	//we have a valid normal closure html tag!
	                    {
	                        this.text = this.text.fInsert(("\n\t\n</" + strtag + ">"), this.cursor);
	                        this.cursor += 2
	                    }
	                    else if (closure == "self")	//we have a valid self-closure html tag!
	                        this.text = this.text.fInsert("/", this.cursor - 1);
	                }
	            }
	        }
	        else if (ch == '"' || ch == "'")
	        {
	            this.text = this.text.fInsert(ch, this.cursor);

	        }
	        else if (ch == "<")
	        {
	            this.intellilist = this.htmlTags;
	        }
	    }
	    catch (exc) { alert(exc); }
	    
	    if (this.onTextChanged) this.onTextChanged(this.text);
	    if (this.onKeyPress) this.onKeyPress(key);
	}

	this.render = function ()
	{

	    if (!this.ctx) return;
	    this.ctx.font = this.font;
	    this.ctx.textBaseline = "top";
	    this.ctx.fillStyle = "white"; // this.back_color;

	    //alert('textarea fillRecting at left, top: ' + this.left + this.top);

	    this.ctx.fillRect
        (
            this.left,
            this.top,
            Math.max(this.minimumWidth, this.getWidth()),
            Math.max(this.minimumHeight, this.getHeight())
        );


	    var x = this.left + this.margin_left; //current X pos initialization
	    var y = this.top + this.margin_top; //current Y pos initialization
	    var line_offset = this.fixed_char_height * this.line_spacing;
	    var char_offset = this.fixed_char_width * this.character_spacing;
	    var tab_offset = char_offset * 4;
	    var txt = "";

	    // draw text		
	    for (var i = 0; i < this.text.length; i++)
	    {
	        ch = this.text[i];
	        if (ch == "\n")
	        {
	            x = this.left + this.margin_left;
	            y += line_offset;
	        }
	        else
	            if (ch == "\t") x += tab_offset;
	            else
	            {
	                if (hasValue(this.selection_start) && hasValue(this.selection_end) && (i >= Math.min(this.selection_start, this.selection_end)) && (i < Math.max(this.selection_start, this.selection_end)))
	                {
	                    this.ctx.fillStyle = this.selection_color;
	                    this.ctx.fillRect(x, y, this.fixed_width_characters ? char_offset : this.ctx.measureText(ch).width, line_offset);
	                    this.setHasFocus(false);
	                }
	                this.ctx.fillStyle = this.fore_color;
	                var xx = this.fixed_width_characters ? x + ((char_offset - this.ctx.measureText(ch).width) / 2) : x;
	                this.ctx.fillText(ch, xx, y);
	                x += this.fixed_width_characters ? char_offset : this.ctx.measureText(ch).width;
	            }
	    }

	    //draw cursor
	    if (this.getHasFocus())
	    {
	        try
	        {
	            var cursor_line_text = this.text.split("\n")[this.getRowCursor()];
	            var xx = this.left + this.margin_left + (this.fixed_width_characters ? (char_offset * this.getColCursor()) : this.ctx.measureText(cursor_line_text.substring(0, this.getColCursor())).width);
	            var yy = this.top + this.margin_top + line_offset * this.getRowCursor();
	            this.ctx.fillStyle = this.cursor_color;
	            this.ctx.fillText(this.cursor_char, xx, yy); //drawing cursor
	        }
	        catch (exc) { alert(exc); }
	    }
	}
	
	//this.render();
}





